Part 2|第 4/30 篇
今日要做的事: 用 AI Studio 測 Vision;把外食與自煮拆成兩條資料流。
今天要解決的目的: Day 2 定好庫存那 0.3 的資料形狀;今天補上外食餐點的紀錄,讓沒開火的日子也能進入 completeness 與 balance_response。
真實流程不是每天打開冰箱才決定吃什麼。沒時間、沒體力就外食;有餘裕,才會打開冰箱讓 DishFlow 幫我組下一餐。
| 項目 | 內容 |
|---|---|
| 產出 | 餐點/冰箱兩支 Prompt、兩條資料流、三輪實測 |
| 工具 | AI Studio(Gemini 3.1 Pro Preview);便當照 SO 關、冰箱照 SO 開 |
| 不使用 | Firebase、Storage、Functions、前端 |
| 素材 | 便當 1+冰箱 4;今天實測 3 輪,另 2 張留給 Day 7 |
| 指標 | completeness 的餐點 tags(外食日也算)、balance_response、no_hallucination |
直接輸入,不上雲。 先在零基礎設施下量 Vision,才分得出錯在模型還是之後的 App。

圖:Day 2 已定好庫存 0.3 的資料形狀;餐點 tags 0.3 若外食不進系統就是空的。評分仍要算外食日的 tags,不然 balance_response 沒有輸入。
前兩天我一直從「冰箱裡有什麼」出發,但正常人不是每天都有時間和體力煮飯。真實流程更像這樣:
今天有時間、有體力 ──▶ 打開冰箱 ──▶ DishFlow 用庫存規劃自煮
今天沒時間、沒體力 ──▶ 外食/超商 ──▶ 記下吃了什麼,不扣家中庫存
所以外食不是例外模式,也不是「今天沒有使用 DishFlow」。它仍然要留下餐點 tags,算進當天的完整性;之後有空自煮時,系統才知道前幾餐吃了什麼,能往互補方向推薦。差別只在於:外食寫入飲食紀錄,自煮還會動到庫存。
這也表示產品不能要求「每天先拍冰箱」。冰箱照是進貨、清點或定期校正庫存時才需要;外食日只記餐點即可。少一步,才比較像真的生活。
另外,台灣餐桌對英文訓練資料也不友善(隔層便當、滷味、三角飯糰)。拿 salad bowl 的 Prompt 來問,模型會很有禮貌地裝懂。
)
圖:外食只寫 tags、不碰 pantry;自煮還要扣庫存。分錯會誤扣或庫存永遠不清。
所以 food_source 必須是欄位:home | convenience_store | supermarket | eat_out。
這張圖畫的是「吃完之後資料往哪裡走」,不是要求使用者每餐都先拍冰箱。使用者先依時間、體力決定外食或自煮;照片辨識負責把結果送進正確的資料流。

圖:餐盒+塑膠袋 vs 自家碗盤,視覺線索夠清楚。
兩種照片、兩套設定:
| 設定 | 便當照(餐點) | 冰箱照(庫存) |
|---|---|---|
| Structured outputs | 關 | 開(沿用 Day 2 pantry schema) |
| Temperature | 0 | 0 |
| Thinking | Medium | Medium |
| Tools | 全關 | 全關 |
便當照要關:今天要看它會不會把醬色講成炸、會不會幫沒入鏡的格子補完。一開 schema,這些裝懂容易被「填滿欄位」蓋掉。
冰箱照要開:Day 2 已經定好 unit enum 與 pantry 欄位,今天直接沿用。Structured outputs 只能固定形狀,不能保證辨識正確;醬料層把味霖、醬油寫成 drink,就是「格式正確、內容錯誤」的例子。
你在看一張台灣餐點照片。
列出:主食、主菜、配菜、烹調方式。
判斷 food_source:外食(一次性餐盒、店家包裝)還是自煮(自家碗盤、鍋具)。
只描述看得到的東西,沒看到的格子不要推測。
不確定的放到 uncertain_items。
不要給營養建議。用繁體中文。


| 欄位 | 輸出 |
|---|---|
| 主食 | 白飯(主菜下方部分露出) |
| 主菜 | 帶皮禽肉(深褐油亮) |
| 配菜 | 炒高麗菜、四季豆、豆干 |
| 烹調 | 烤或滷/炒 |
food_source |
外食 |
uncertain_items |
燒鴨或油雞,無法百分百確認 |
三個重點:沒把醬色當炸;沒幫白飯那格補完;禽肉停在 uncertain。收成:
{
"slot": "lunch",
"label": "鴨腿便當",
"food_source": "eat_out",
"tags": ["braised", "poultry", "vegetable_present", "sauce_heavy"],
"uncertain_items": ["烹調:烤或滷", "禽肉:燒鴨或油雞"],
"needs_confirmation": true
}
braised 就夠:對 balance_response 重要的是「不是炸」。sauce_heavy 不是「鈉高」,只標看得到的。

圖:餐點照=消費紀錄;冰箱照=庫存快照。產品上冰箱是進貨/每週掃,不是每餐全層辨識。今天只做壓力測試。
kind(食材/調味/飲料/非食物)由模型看;priority: staple 由 Rule Engine 對。完整冰箱 Prompt 見文末附錄。
一張一張丟。 一次四張會混層,Day 7 題庫就無法歸因。
截圖常常裁掉回答本體,所以模型輸出也要貼進文章。下面兩段都保留 Structured outputs 實際產生的 JSON,不人工改格式。
目前結果是便當過、醬料翻車;冷凍海鮮的主要食材辨識過關,但漏掉粉紅色封口夾。
醬料層(翻車)


模型的 Structured outputs 原始 JSON:
{
"items": [
{
"name": "紅蓋罐裝醬料",
"qty": 1,
"unit": "each",
"qty_text": "畫面中清楚可見一個完整的玻璃罐",
"approx": true,
"kind": "seasoning",
"location": "fridge"
},
{
"name": "星巴克蜜桃蘋果優格",
"qty": 1,
"unit": "each",
"qty_text": "畫面中清楚可見一個帶有紙套的杯裝優格",
"approx": true,
"kind": "ingredient",
"location": "fridge"
},
{
"name": "NIPPON PREMIUM 富良野哈密瓜沙瓦",
"qty": 1,
"unit": "each",
"qty_text": "畫面中清楚可見一個易開罐",
"approx": true,
"kind": "drink",
"location": "fridge"
},
{
"name": "紙盒裝飲品",
"qty": 1,
"unit": "box",
"qty_text": "畫面中可見一個帶有圓形瓶蓋的紙盒包裝",
"approx": true,
"kind": "drink",
"location": "fridge"
},
{
"name": "紅蓋寶特瓶飲品",
"qty": 1,
"unit": "each",
"qty_text": "畫面中可見一個完整的寶特瓶",
"approx": true,
"kind": "drink",
"location": "fridge"
},
{
"name": "金黃蓋寶特瓶飲品",
"qty": 1,
"unit": "each",
"qty_text": "畫面中可見一個被部分遮擋的寶特瓶",
"approx": true,
"kind": "drink",
"location": "fridge"
}
],
"uncertain_items": []
}
| 實際 | 模型 | 判定 |
|---|---|---|
| 香蔥鵝油 | 紅蓋罐裝醬料/seasoning | 半過 |
| 優格/沙瓦 | 品牌正確 | 過 |
| 牛奶 | 紙盒裝飲品/drink | 半過 |
| 味霖、日式醬油 | 寶特瓶飲品/drink | 翻車 |
| uncertain | [] |
裝懂 |
味霖/醬油變 drink → 調味料從可用清單消失,leftover_hit 失真。JSON 形狀再漂亮,kind 錯了後面規則也救不回來。
冷凍海鮮(主要食材過關,漏掉一項)

重新開啟 Structured outputs 後,模型的原始 JSON:
{
"items": [
{
"name": "Kirkland 冷凍生蝦",
"qty": 1,
"unit": "bag",
"qty_text": "畫面左側看到一包藍色包裝的冷凍蝦",
"approx": true,
"kind": "ingredient",
"location": "freezer"
},
{
"name": "生凍花枝排",
"qty": 1,
"unit": "bag",
"qty_text": "畫面右側看到一包透明夾鏈袋裝,標籤寫著生凍花枝排",
"approx": true,
"kind": "ingredient",
"location": "freezer"
}
],
"uncertain_items": [
"被蝦子和花枝排擋住的後方物品,因被包裝遮擋無法辨識具體品種。"
]
}
蝦(crustacean)與花枝排(mollusk)分開、freezer 正確,後方遮擋物也進了 uncertain。Golden 題最重要的一步成立:過敏只擋蝦、放行花枝排。
但它漏掉畫面中的粉紅色封口夾。這不會污染 pantry,因為夾子本來就不該入庫;仍要記成 Vision 的漏偵測。數量也只到 1 bag,看不出剩餘比例,粗單位仍要人確認。
附帶修正 Day 1:冷凍蝦不會兩天到期;解凍進冷藏才是壓力來源。
今天兩張冰箱照其實已經在考「真實拍攝」,不是棚拍:
| 條件 | 這批照片裡長什麼樣 | 後果 |
|---|---|---|
| 光線 | 冰箱內燈偏黃/不均,冷凍層更暗 | 標籤對比差,文字難讀 |
| 遮擋 | 手、封口夾、前後疊放、結霜 | 品名讀不全,只好猜或裝懂 |
| 隨意構圖 | 紙盒被裁切、瓶子只露蓋子 | 牛奶變「紙盒裝飲品」、醬油變「金黃蓋飲品」 |
以後拍給 Vision 的庫存照要更清楚: 單層、正面、標籤朝鏡頭、少用手擋、能開燈就開。壓力測試可以故意拍壞(Day 5/Day 7 題庫需要),但當「正式入庫」的照片,清楚度就是資料品質。隨意拍不是生活感,是在把錯誤寫進 pantry。
另兩張題庫(Day 7 再測)
袋裝青菜(硬猜品種?)/花椰菜米(portion+MICROWAVE)。原圖:day3-05、day3-06。
day3-02 便當 food_source=eat_out,禽肉 uncertain 沒裝懂
day3-03 醬料層 味霖/醬油→drink,uncertain=[] 裝懂
day3-04 冷凍海鮮 蝦/花枝排分開,漏掉封口夾 沒裝懂
day3-05 袋裝菜 (待測)
day3-06 花椰菜米 (待測)
每列加記:光源、距離、有沒有遮擋標籤。
| 翻車 | 今天 | 後果 |
|---|---|---|
| 醬色當炸 | 無 | 晚餐一路往清淡偏 |
| 禽肉硬猜 | 無 | 污染 today_meals |
| 調味料→drink | 有 | leftover_hit 失真 |
| 用不確定名裝確定 | 有 | uncertain 失效 |
| 外食認成自煮 | 無 | 誤扣庫存 |
| 沒入鏡硬掰 | 無 | 發明白飯比例 |
| 看得到的非食物漏列 | 有(粉紅封口夾) | 不污染庫存,但代表偵測不完整 |
| 光線差/遮擋/裁切 | 有(醬料層尤明顯) | 文字讀不到 → 泛稱+kind 猜錯 |
先改 Prompt,不要 Fine-tune。 下一刀大概是:寶特瓶褐色/淡色液體,優先當調味料。還裝懂就交給 Day 5 的 needs_confirmation。拍照規範跟 Prompt 一樣重要:輸入糊,輸出再漂亮也只是漂亮的錯。
錯一次會污染後面 3~7 天的 balance_summary。照片先放本地 meals/raw/,Cloud Storage 是 Day 10 的事。
balance_response 沒輸入。下一篇: 今天只替冰箱庫存套用 Day 2 的欄位;Day 4 要正式替餐點照設計 Meal JSON,讓 food_source、tags 與 uncertain 都能穩定寫入。
冰箱照請 打開 Structured outputs,欄位對齊下方列表(與 Day 2 unit enum 一致)。Prompt 本文:
你在看一張我家冰箱的照片。
列出畫面中看得到的每一項,每項給我:
- name:品項名稱
- qty + unit:unit 只能用 each / piece / slice / box / pack / bag /
bunch / handful / fist / palm / cup / tbsp / tsp / ml / portion
- qty_text:你怎麼判斷這個量(例如「拆封剩約一半」)
- approx:一律 true
- kind:ingredient 食材 / seasoning 調味料 / drink 飲料 / non_food 非食物
- location:fridge 或 freezer,從結霜與抽屜位置判斷
規則:
1. 被袋子、包裝或手遮住而認不出來的,不要猜具體品種,放到 uncertain_items。
2. 數量只看得到一部分時,就用看得到的數字,不要推測後面還有幾個。
3. 沒有看到日期就不要編到期日。
4. 不要推薦菜色,也不要給營養建議。
用繁體中文。